In this tutorial, we will learn how to upload and download a file using Spring Boot RESTful API. Uploading and downloading files are very common tasks for which developers need to write code in their applications.
This Spring Boot project is upgraded to Spring Boot 3 and Java 17.
Learn and master in Spring boot at Spring Boot Tutorial
We’ll first build the REST APIs for uploading and downloading files, and then test those APIs using Postman.
The source code of this tutorial available on my GitHub Repository.
Let’s get started.
There are many ways to create aSpring Boot
application. The simplest way is to use Spring Initializr at
http://start.spring.io/, which is an online Spring Boot application generator.
Let'specify the following details:
Once, all the details are entered, click on the Generate Project button will generate a spring boot project then download it. That’s it! You may now unzip the downloaded application archive and import it into your favorite IDE.
Below, the diagram shows a project structure for reference:
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.javaguides.springboot</groupId>
<artifactId>springboot-upload-download-file-rest-api-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot-upload-download-file-rest-api-example</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.4</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Let’s configure our Spring Boot application to enable Multipart file uploads, and define the maximum file
size that can be uploaded. We’ll also configure the directory into which all the uploaded files will be
stored.
Open src/main/resources/application.properties file, and add the following properties to it -
## MULTIPART (MultipartProperties)
# Enable multipart uploads
spring.servlet.multipart.enabled=true
# Threshold after which files are written to disk.
spring.servlet.multipart.file-size-threshold=2KB
# Max file size.
spring.servlet.multipart.max-file-size=200MB
# Max Request Size
spring.servlet.multipart.max-request-size=215MB
## File Storage Properties
file.upload-dir=./uploads
server.port=8081
Spring Boot has an awesome feature called @ConfigurationProperties using which you can
automatically bind the properties defined in the application.properties file to a POJO class.
Let’s define a POJO class called FileStorageProperties inside
net.javaguides.springboot.fileuploaddownload package to bind all the file storage properties -
package net.javaguides.springboot.fileuploaddownload.property;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "file")
public class FileStorageProperties {
private String uploadDir;
public String getUploadDir() {
return uploadDir;
}
public void setUploadDir(String uploadDir) {
this.uploadDir = uploadDir;
}
}
Let’s now write the REST APIs for uploading single as well as multiple files. Create a new controller class called FileUploadController inside net.javaguides.springboot.fileuploaddownload.controller package and add following code to it:
package net.javaguides.springboot.fileuploaddownload.controller;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import net.javaguides.springboot.fileuploaddownload.payload.Response;
import net.javaguides.springboot.fileuploaddownload.service.FileStorageService;
@RestController
public class FileUploadController {
@Autowired
private FileStorageService fileStorageService;
@PostMapping("/uploadFile")
public Response uploadFile(@RequestParam("file") MultipartFile file) {
String fileName = fileStorageService.storeFile(file);
String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/downloadFile/")
.path(fileName)
.toUriString();
return new Response(fileName, fileDownloadUri,
file.getContentType(), file.getSize());
}
@PostMapping("/uploadMultipleFiles")
public List < Response > uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
return Arrays.asList(files)
.stream()
.map(file - > uploadFile(file))
.collect(Collectors.toList());
}
}
Let’s now write the REST API for downloading a file. Create a new controller class called FileDownloadController inside net.javaguides.springboot.fileuploaddownload.controller package and add following code to it:
package net.javaguides.springboot.fileuploaddownload.controller;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import net.javaguides.springboot.fileuploaddownload.service.FileStorageService;
@RestController
public class FileDownloadController {
private static final Logger logger = LoggerFactory.getLogger(FileDownloadController.class);
@Autowired
private FileStorageService fileStorageService;
@GetMapping("/downloadFile/{fileName:.+}")
public ResponseEntity < Resource > downloadFile(@PathVariable String fileName, HttpServletRequest request) {
// Load file as Resource
Resource resource = fileStorageService.loadFileAsResource(fileName);
// Try to determine file's content type
String contentType = null;
try {
contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
} catch (IOException ex) {
logger.info("Could not determine file type.");
}
// Fallback to the default content type if type could not be determined
if (contentType == null) {
contentType = "application/octet-stream";
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
}
}
This Response class used to return responses from the /uploadFile and /uploadMultipleFiles APIs.
Let's create Response class inside net.javaguides.springboot.fileuploaddownload.payload package with the following contents:
public class Response {
private String fileName;
private String fileDownloadUri;
private String fileType;
private long size;
public Response(String fileName, String fileDownloadUri, String fileType, long size) {
this.fileName = fileName;
this.fileDownloadUri = fileDownloadUri;
this.fileType = fileType;
this.size = size;
}
// Getters and Setters (Omitted for brevity)
}
Let’s now write the service for storing files in the file system and retrieving them.
Let's create a new class called FileStorageService.java inside net.javaguides.springboot.fileuploaddownload.service with the following contents -
package net.javaguides.springboot.fileuploaddownload.service;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import net.javaguides.springboot.fileuploaddownload.exception.FileStorageException;
import net.javaguides.springboot.fileuploaddownload.exception.FileNotFoundException;
import net.javaguides.springboot.fileuploaddownload.property.FileStorageProperties;
@Service
public class FileStorageService {
private final Path fileStorageLocation;
@Autowired
public FileStorageService(FileStorageProperties fileStorageProperties) {
this.fileStorageLocation = Paths.get(fileStorageProperties.getUploadDir())
.toAbsolutePath().normalize();
try {
Files.createDirectories(this.fileStorageLocation);
} catch (Exception ex) {
throw new FileStorageException("Could not create the directory where the uploaded files will be stored.", ex);
}
}
public String storeFile(MultipartFile file) {
// Normalize file name
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
try {
// Check if the file's name contains invalid characters
if (fileName.contains("..")) {
throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName);
}
// Copy file to the target location (Replacing existing file with the same name)
Path targetLocation = this.fileStorageLocation.resolve(fileName);
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
return fileName;
} catch (IOException ex) {
throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex);
}
}
public Resource loadFileAsResource(String fileName) {
try {
Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (resource.exists()) {
return resource;
} else {
throw new FileNotFoundException("File not found " + fileName);
}
} catch (MalformedURLException ex) {
throw new FileNotFoundException("File not found " + fileName, ex);
}
}
}
The FileStorageService class throws some exceptions in case of unexpected situations. The following are the definitions of those exception classes (All the exception classes go inside the package net.javaguides.springboot.fileuploaddownload.exception).
package net.javaguides.springboot.fileuploaddownload.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class FileNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1 L;
public FileNotFoundException(String message) {
super(message);
}
public FileNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
package net.javaguides.springboot.fileuploaddownload.exception;
public class FileStorageException extends RuntimeException {
private static final long serialVersionUID = 1 L;
public FileStorageException(String message) {
super(message);
}
public FileStorageException(String message, Throwable cause) {
super(message, cause);
}
}
To know more about exception handling in Spring boot then check out Spring Boot 2 Exception Handling for REST APIs
Note that, we’ve annotated the above exception class with @ResponseStatus(HttpStatus.NOT_FOUND) . This ensures that Spring boot responds with a 404 Not Found status when this exception is thrown.
We’re done developing our backend APIs. Let’s run the application and test the APIs via Postman. Type the following command from the root directory of the project to run the application -
mvn spring-boot:run
Once started, the application can be accessed at http://localhost:8081
That's it. We learned how to upload single as well as multiple files via REST APIs written in Spring Boot. We also learned how to download files in Spring Boot.
I hope the post was helpful to you. You can download the entire code for the project that we built in this article from the GitHub repository.